<?php
namespace DRY\Module;

/**
 * DonReY Framework 2012 :: XML module - Various tools to read and write XML, convert PHP internal formats to an XML string
 * This is a STATIC module, it doesn't need initializing and configuration, and is always available ( however, it's only loaded at first use, like any other module )
 * The XMLWriter part of this module can only process one XML at a time, since it uses static storage.
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Structure/Modules/xml.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 */
class XML extends base
{
	// Exception/Error codes
	const
		EXC_UNKNOWN = 0
		;

	/**
	 * The XML Writer Object
	 * @var XMLWriter
	 */
	protected static $xmlw;

	/**
	 * Mode of operation ( True = stream, False = memory )
	 * @var bool
	 */
	protected static $xmlw_mode;

	/**
	 * Initializes the XMLWriter and basic elements of the document
	 *
	 * @param string $write_file File or Stream to use in Stream mode
	 * @param array $root_element If present, write a DocType
	 * @param bool $humanize Indent the XML text to make it easy for humans to read/modify
	 * @param bool $standalone If this XML document has the "standalone" attribute (True = "yes", False = "no") - if missing, standalone is not used
	 */
	public static function &init_XMLWriter($write_file = null, $root_element = null, $humanize = true, $standalone = null)
	{
		self::$xmlw = new \XMLWriter;
		self::$xmlw_mode = !is_null($write_file);

		if(is_null($write_file)) {
			self::$xmlw-> openMemory();
		} else {
			self::$xmlw-> openUri($write_file);
		}
		if($humanize) {
			self::$xmlw-> setIndent(true);
			self::$xmlw-> setIndentString("\t");
		}
		if(!is_null($root_element)) {
			self::$xmlw-> writeDTD($root_element);
		}
		// begin the document
		self::$xmlw-> startDocument('1.0', 'UTF-8', is_null($standalone)?null:($standalone?"yes":"no"));
		return self::$xmlw;
	}

	/**
	 * After the source has been transformed into XML using the "from_{type of source}()" methods, this will return the XML string or flush it
	 */
	public static function write_XML()
	{
		// complete the document
		self::$xmlw-> endDocument();
		if(self::$xmlw_mode)
			return self::$xmlw-> flush();
		else
			return self::$xmlw-> outputMemory();
	}

	/**
	 * Write a SimpleXML node with it's attributes and text content, and go recursively into children
	 * (Note that this will completely ignore any comment nodes in the source, as they cannot be accessed using the SimpleXML API.)
	 * @param SimpleXMLElement $xml
	 */
	public static function from_SimpleXML(SimpleXMLElement $xml)
	{
		self::$xmlw-> startElement($xml-> getName());

		// attributes
		foreach($xml-> attributes() as $name=> $value)
			self::$xmlw-> writeAttribute($name, $value);

		// textnode
		$text_content = (string)$xml;
		if(strlen($text_content) > 0)
			self::$xmlw-> text($text_content);

		// children
		if($xml-> count() > 0) {
			foreach($xml-> children() as $child)
				self::from_SimpleXML($child);
		}

		self::$xmlw-> endElement();
	}

	/**
	 * Iterate through an array's members and go recursively for those that are arrays themselves
	 * @param array $array
	 * @param string $element_name
	 * @param bool $skip_empty True = Don't add empty children of this array in the XML
	 */
	public static function from_Array($array, $element_name, $skip_empty = false)
	{
		if(!is_numeric($element_name))
			self::$xmlw-> startElement($element_name);

		// attributes
		if(array_key_exists('__attributes', $array)) {
			if(is_array($array['__attributes']))
				foreach($array['__attributes'] as $name=> $value)
					if(!is_numeric($name))
						self::$xmlw-> writeAttribute($name, $value);
			unset($array['__attributes']);
		}

		// array members become children
		$c = 0;
		foreach($array as $child_name=> $content) {
			if($skip_empty && empty($content))
				continue;
			if(is_array($content)) {
				// sub-array
				if(is_numeric($child_name)) {
					self::from_Array($content, 0, $skip_empty);
					if($c < count($array)-1) {
						self::$xmlw-> endElement();
						self::$xmlw-> startElement($element_name);
					}
				} else
					self::from_Array($content, $child_name, $skip_empty);
			} elseif (is_numeric($child_name)) {
				// basic text node (numeric array index, content is not an array)
				self::scalar_Content($content);
			} else {
				// named child containing only a scalar value
				self::$xmlw-> startElement($child_name);
				self::scalar_Content($content);
				self::$xmlw-> endElement();
			}
			$c++;
		}

		if(!is_numeric($element_name))
			self::$xmlw-> endElement();
	}

	/**
	 * Writes a scalar content either as simple text or as CDATA (actually, it's the same thing, however by using CDATA htmlentities are not encoded)
	 * @param mixed $content
	 */
	public static function scalar_Content($content)
	{
		if(strpos($content, '&')!==false || strpos($content, '<')!==false || strpos($content, '>')!==false) // let " ( &quot; ) and ' ( &apos; ) be encoded by XMLWriter, no need for CDATA section on those
			self::$xmlw-> writeCdata((string)$content);
		else
			self::$xmlw-> text((string)$content);
	}

}